/**
 * This file is part of 1genia trampoline
 * Copyright (C) 2007 - 2008 1genia (contact@1genia.com)
 *
 * This library is free software; you can redistribute it and/or modify
 * it under the terms of the GNU Lesser General Public License as
 * published by the Free Software Foundation; version 3 of the License. 
 *
 * This library is distributed in the hope that it will be useful, but
 * WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 * Library General Public License for more details. 
 *
 * You should have received a copy of the GNU Lesser General Public
 * License along with this library; see the file COPYING.TXT.  If not,
 * write to the Free Software Foundation, Inc., 51 Franklin Street,
 * Fifth Floor, Boston, MA 02110-1301, USA. 
 **/

package com.genia.toolbox.web.flow;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.HashSet;
import java.util.Set;

import org.springframework.webflow.definition.FlowDefinition;
import org.springframework.webflow.definition.registry.FlowDefinitionConstructionException;
import org.springframework.webflow.definition.registry.FlowDefinitionHolder;
import org.springframework.webflow.definition.registry.FlowDefinitionRegistry;
import org.springframework.webflow.definition.registry.FlowDefinitionRegistryImpl;
import org.springframework.webflow.definition.registry.NoSuchFlowDefinitionException;

/**
 * a {@link FlowDefinitionRegistry} that allow registering of other
 * {@link FlowDefinitionRegistry}.
 */
public class RegistrableFlowDefinitionRegistery
    implements FlowDefinitionRegistry
{
  /**
   * the basic {@link FlowDefinitionRegistry} that allows to register new flows.
   */
  private final FlowDefinitionRegistry baseFlowDefinitionRegistry = new FlowDefinitionRegistryImpl();

  /**
   * the parent of this {@link FlowDefinitionRegistry}.
   */
  private FlowDefinitionRegistry parent;

  /**
   * the list of registered {@link FlowDefinitionRegistry}.
   */
  private Set<FlowDefinitionRegistry> registeredFlows = null;

  /**
   * the flow that are pending registration.
   */
  private Collection<FlowDefinitionRegistry> pendingRegistredFlows = new ArrayList<FlowDefinitionRegistry>();



  /**
   * setter for the registeredFlows property.
   * 
   * @param registeredFlows
   *          the registeredFlows to set
   */
  public void setRegisteredFlows(Collection<FlowDefinitionRegistry> registeredFlows)
  {
    this.pendingRegistredFlows = registeredFlows;
    registeredFlows = null;
  }



  /**
   * returns the registered flows.
   * 
   * @return the registered flows
   */
  private Set<FlowDefinitionRegistry> getRegisteredFlows()
  {
    if (this.registeredFlows == null) {
      this.registeredFlows = new HashSet<FlowDefinitionRegistry>();
      this.registeredFlows.add(baseFlowDefinitionRegistry);
      this.registeredFlows.addAll(pendingRegistredFlows);
      this.registeredFlows.remove(this);
    }
    return registeredFlows;
  }



  /**
   * Lookup the flow definition with the specified id.
   * 
   * @param id
   *          the flow definition identifier
   * @return the flow definition
   * @throws NoSuchFlowDefinitionException
   *           when the flow definition with the specified id does not exist
   * @throws FlowDefinitionConstructionException
   *           if there is a problem constructing the identified flow definition
   */
  public FlowDefinition getFlowDefinition(String id)
      throws NoSuchFlowDefinitionException, FlowDefinitionConstructionException
  {
    for (FlowDefinitionRegistry registry : getRegisteredFlows()) {
      try {
        return registry.getFlowDefinition(id);
      }
      catch (NoSuchFlowDefinitionException e) {
        // Do nothing.
      }
    }
    throw new NoSuchFlowDefinitionException(id);
  }



  /**
   * Returns the number of flow definitions registered in this registry.
   * 
   * @return the flow definition count
   */
  public int getFlowDefinitionCount()
  {
    int res = 0;
    for (FlowDefinitionRegistry registry : getRegisteredFlows()) {
      res += registry.getFlowDefinitionCount();
    }
    return res;
  }



  /**
   * Returns the ids of the flows registered in this registry.
   * 
   * @return the flow definition ids
   */
  public String[] getFlowDefinitionIds()
  {
    Set<String> ids = new HashSet<String>();
    for (FlowDefinitionRegistry registry : getRegisteredFlows()) {
      ids.addAll(Arrays.asList(registry.getFlowDefinitionIds()));
    }
    return ids.toArray(new String[ids.size()]);
  }



  /**
   * Returns this registry'es parent registry.
   * 
   * @return the parent flow definition registry, or null if no parent is set
   */
  public FlowDefinitionRegistry getParent()
  {
    return parent;
  }



  /**
   * Register a flow definition in this registry.
   * 
   * @param definition
   *          the actual flow definition
   */
  public void registerFlowDefinition(FlowDefinition definition)
  {
    baseFlowDefinitionRegistry.registerFlowDefinition(definition);
  }



  /**
   * Register a flow definition in this registry. Registers a "holder", not the
   * Flow definition itself. This allows the actual Flow definition to be loaded
   * lazily only when needed, and also rebuilt at runtime when its underlying
   * resource changes without re-deploy.
   * 
   * @param definitionHolder
   *          a holder holding the flow definition to register
   */
  public void registerFlowDefinition(FlowDefinitionHolder definitionHolder)
  {
    baseFlowDefinitionRegistry.registerFlowDefinition(definitionHolder);
  }



  /**
   * Sets this registry's parent registry. When asked by a client to locate a
   * flow definition this registry will query it's parent if it cannot fulfill
   * the lookup request itself.
   * 
   * @param parent
   *          the parent flow definition registry, may be null
   */
  public void setParent(FlowDefinitionRegistry parent)
  {
    this.parent = parent;
  }
}
